﻿using log4net;
using System;
using System.IO;
using System.Text;
using Velocity4Net.Ctx;
using Velocity4Net.Runtime.Parser.Node;
using Velocity4Net.Util;

namespace Velocity4Net.Runtime.Directives
{

    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */













    /**
     * Directive that puts an unrendered AST block in the context
     * under the specified key, postponing rendering until the
     * reference is used and rendered.
     *
     * @author Andrew Tetlaw
     * @author Nathan Bubna
     * @author <a href="mailto:wyla@removethis.sci.fi">Jarkko Viinamaki</a>
     * @since 1.7
     * @version $Id: Block.java 686842 2008-08-18 18:29:31Z nbubna $
     */
    public abstract class Block : Directive
    {
        protected Node block;
        protected ILog log;
        protected int maxDepth;
        protected String key;

        /**
         * Return type of this directive.
         * @return type, DirectiveConstants.BLOCK or DirectiveConstants.LINE
         */
        public int getType()
        {
            return BLOCK;
        }

        /**
         *  simple init - get the key
         *  @param rs
         *  @param context
         *  @param node
         */
        public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
        {
            super.init(rs, context, node);

            log = rsvc.getLog();

            /**
             * No checking is done. We just grab the last child node and assume
             * that it's the block!
             */
            block = node.jjtGetChild(node.jjtGetNumChildren() - 1);
        }

        /**
         * renders block directive
         * @param context
         * @param writer
         * @return success status
         */
        public bool render(InternalContextAdapter context, TextWriter writer)
        {
            preRender(context);
            try
            {
                return block.render(context, writer);
            }
            catch (IOException e)
            {
                String msg = "Failed to render " + id(context) + " to writer at " +
                    StringUtils.formatFileString(this);
                log.Error(msg, e);
                throw new SystemException(msg, e);
            }
            catch (StopCommand stop)
            {
                if (!stop.isFor(this))
                {
                    throw stop;
                }
                return true;
            }
            finally
            {
                postRender(context);
            }
        }

        /**
         * Creates a string identifying the source and location of the block
         * definition, and the current template being rendered if that is
         * different.
         * @param context
         * @return id string
         */
        protected String id(InternalContextAdapter context)
        {
            StringBuilder str = new StringBuilder(100)
                .Append("block $").Append(key);
            if (!context.getCurrentTemplateName().Equals(getTemplateName()))
            {
                str.Append(" used in ").Append(context.getCurrentTemplateName());
            }
            return str.ToString();
        }

        /**
         * actual class placed in the context, holds the context
         * being used for the render, as well as the parent (which already holds
         * everything else we need).
         */
        public static class Reference : Renderable
        {
            private InternalContextAdapter context;
            private Block parent;
            private int depth;

            /**
             * @param context
             * @param parent
             */
            public Reference(InternalContextAdapter context, Block parent)
            {
                this.context = context;
                this.parent = parent;
            }

            /**
             * Render the AST of this block into the writer using the context.
             * @param context
             * @param writer
             * @return  success status
             */
            public bool render(InternalContextAdapter context, TextWriter writer)
            {
                depth++;
                if (depth > parent.maxDepth)
                {
                    /* this is only a debug message, as recursion can
                     * happen in quasi-innocent situations and is relatively
                     * harmless due to how we handle it here.
                     * this is more to help anyone nuts enough to intentionally
                     * use recursive block definitions and having problems
                     * pulling it off properly.
                     */
                    parent.log.Debug("Max recursion depth reached for {} at {}", parent.id(context), StringUtils.formatFileString(parent));
                    depth--;
                    return false;
                }
                else
                {
                    parent.render(context, writer);
                    depth--;
                    return true;
                }
            }

            /**
             * Makes #if( $blockRef ) true without rendering, so long as we aren't beyond max depth.
             * @return reference value as bool
             */
            public bool getAsBoolean()
            {
                return depth <= parent.maxDepth;
            }

            /**
             * @return rendered string
             */
            public String toString()
            {
                TextWriter writer = new StringWriter();
                if (render(context, writer))
                {
                    return writer.ToString();
                }
                return null;
            }
        }
    }
}